<?php
namespace service\http;

use my\rsa\RSA;
use think\Cache;

/**
 * Class Safe
 * @package my\http
 *
 * 客户端第一次请求调用创建Token接口，获取Token，随后所有请求接口在HTTP报头加入Token值和Sign值，否则报Token或Sign错误
 * 构建签名字符串：token值+接口名称去斜杠+参数列表按键的字母升序排序+"jinglingqiu"
 * Sign = md5(签名字符串)
 * 注:GET和POST参数都同时加入签名,特殊参数如文件上传的files参数不加入签名
 * 例如:
 * token=123456,api=api/user/cmsLogin,param={name=hello,age=25,money=180}
 * Sign = md5(123456apiusercmsLoginage25money180namehellojinglingqiu)
 */
class Safe {

    const SUCCESS = 0;      //安全通过
    const TOKEN_ERROR = 1;  //令牌错误
    const SIGN_ERROR = 2;   //签名错误
    const REPEAT_ERROR = 3; //重复错误，同一个令牌同一个签名如果N秒内重复请求，报此错误，可在config设置，默认3秒

    private $userAgent;     //请求浏览器信息
    private $api;           //请求接口名称
    private $token;         //令牌
    private $sign;          //签名
    private $params = [];   //请求参数列表

    public function __construct($userAgent, $api, $token, $sign, $params = []) {
        self::setData($userAgent,$api,$token,$sign,$params);
    }

    public function setData($userAgent, $api, $token, $sign, $params = []) {
        $this->userAgent = $userAgent;
        $this->api = $api;
        $this->token = $token;
        $this->sign = $sign;
        $this->params = $params;
        return $this;
    }

    /**
     * 创建令牌
     * @param $userAgent string 请求浏览器信息
     * @return string
     */
    public static function createToken($userAgent = 'unknown') {
        $create_time = time();
        $token = RSA::encrypt_myself($create_time.'<#&&&#>'.md5($userAgent));
        Cache::store('token')->set($token,date('Y-m-d H:i:s',$create_time));
        return $token;
    }

    /**
     * 安全验证
     * @return mixed
     */
    public function validate() {
        if (!config('safe_mode')) {
            return self::SUCCESS;
        }
        if (!$this->userAgent)
            $this->userAgent = 'unknown';
        if (config('safe_param')['validate_token'] && (!$this->token || !$this->validateToken()))
            return self::TOKEN_ERROR;
        if (config('safe_param')['validate_sign'] && (!$this->sign || !$this->validateSign()))
            return self::SIGN_ERROR;
        if (config('safe_param')['validate_repeat'] && !$this->validateRepeat())
            return self::REPEAT_ERROR;
        $this->record();
        return self::SUCCESS;
    }

    /**
     * 验证令牌
     * @return bool
     */
    private function validateToken() {
        $decrypt = RSA::decrypt_myself($this->token);
        $info = explode('<#&&&#>',$decrypt);
        if (count($info) !== 2)
            return false;
        elseif ($info[1] !== md5($this->userAgent))
            return false;
        return Cache::store('token')->has($this->token);
    }

    /**
     * 验证签名
     * @return bool
     */
    private function validateSign() {
        $api = str_replace('/','',$this->api);
        $sort_param = $this->params;
        ksort($sort_param);
        if(isset($sort_param['files']))
            unset($sort_param['files']);
        $buffer = $this->token.$api;
        foreach ($sort_param as $k => $v) {
            $buffer .= $k.$v;
        }
        $buffer .= 'jinglingqiu';
        $sign = md5($buffer);
        return $sign === $this->sign;
    }

    /**
     * 验证重复
     * @return bool TRUE为验证通过(不重复)，FALSE为验证不通过(重复错误)
     */
    private function validateRepeat() {
          $this->ips();
          $request = \think\Request::instance();
          $request->url(true);
//        $cache_key = md5($this->token.'##'.$this->sign);
//        $time = Cache::store('token')->get($cache_key);
//        if (!$time)
//            return true;
//        $compare_time = 3;
//        if (config('safe_param')['repeat_compare_time'])
//            $compare_time = (int)config('safe_param')['repeat_compare_time'];
//        if (time() - $time >= $compare_time){
//            return true;
//        } else {
//            return false;
//        }
    }

    public function ips(){
        if (!empty($_SERVER['HTTP_CLIENT_IP']))
        {
            $ip_address = $_SERVER['HTTP_CLIENT_IP'];
        }
        elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
        {
            $ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        else
        {
            $ip_address = $_SERVER['REMOTE_ADDR'];
        }
        return $ip_address;
    }

    /**
     * 记录本次请求验证的时间
     */
    private function record() {
        $cache_key = md5($this->token.'##'.$this->sign);
        Cache::store('token')->set($cache_key,time());
    }
}